#include <kew.h>
#include <gmp.h>
#include <mpfr.h>

/* This file is mostly macro expansions which wrap GMP functions.
 * Conventions are:
 * I=integer (mpz_t in GMP)
 * Q=rational (mpq_t)
 * F=float (mpfr_t)
 * B=boolean (not in GMP!)
 * Arguments are listed consecutively, followed by _ then results
 * i.e. II_BQ takes 2 integer arguments and returns a boolean and a rational.
 *
 * Arithmetic is done to preserve generality and precision in the results.
 * For example, MyInteger + MyRational results in a rational.
 * Unfortunately, those goals might sometimes be in conflict.
 * MFloat + MyRational ??
 *
 * I2Q, I2F, F2Q
 *
 * FIXME: decide whether the functions defined below are responsible for
 * calling mpz_clear and/or a finalizer associated with the object in which they
 * are stored.
 */

#define value_int(OBJECT) (((KewIntegerObject *) OBJECT)->value)
#define check_int(VAL) kew_type_check((VAL), &KewIntegerType, "integer")

#define DEFINE_I_I(OPNAME,OP) \
KEW_METHOD(OPNAME) \
{ \
    mpz_t result; \
    mpz_init(result); \
    OP(result, value_int(receiver)); \
    results[0] = kew_integer_wrap(result); \
    /* FIXME: it's very unclear from the docs whether I do this: mpz_clear(result); */ \
}

#define DEFINE_II_I(OPNAME,OP) \
KEW_METHOD(OPNAME) \
{ \
    mpz_t result; \
    mpz_init(result); \
    check_int(args[0]); \
    OP(result, value_int(receiver), value_int(args[0])); \
    results[0] = kew_integer_wrap(result); \
    /* FIXME: it's very unclear from the docs whether I do this: mpz_clear(result); */ \
}

#define DEFINE_II_II(OPNAME,OP) \
KEW_METHOD(OPNAME) \
{ \
    mpz_t result1; \
    mpz_t result2; \
    mpz_init(result1); \
    mpz_init(result2); \
    check_int(args[0]); \
    OP(result1, result2, value_int(receiver), value_int(args[0])); \
    results[0] = kew_integer_wrap(result1); \
    results[1] = kew_integer_wrap(result2); \
    /* FIXME: it's very unclear from the docs whether I do this: mpz_clear(result); */ \
}

/* + - * negate abs */
DEFINE_II_I(plus, mpz_add)
DEFINE_II_I(minus, mpz_sub)
DEFINE_II_I(multiply, mpz_mul)
DEFINE_I_I(negate, mpz_neg)
DEFINE_I_I(abs, mpz_abs)

/*
/ (produce rational)
/= (produce quotient rounding to 0)
/+ (produce quotient rounding to +infinity)
/- (produce quotient rounding to -infinity)
%= (produce remainder rounding to 0)
%+ (produce remainder rounding to +infinity)
%- (produce remainder rounding to -infinity)
/%= (produce quotient, remainder rounding to 0)
/%+ (produce quotient, remainder rounding to +infinity)
/%- (produce quotient, remainder rounding to -infinity)
*/

DEFINE_II_Q(divide_rational, mpz_tdiv_q) /* FIXME: I want a fraction here */

DEFINE_II_I(divide_quotient_round_zero, mpz_tdiv_q)
DEFINE_II_I(divide_quotient_round_positive, mpz_cdiv_q)
DEFINE_II_I(divide_quotient_round_negative, mpz_fdiv_q)

DEFINE_II_I(divide_remainder_round_zero, mpz_tdiv_r)
DEFINE_II_I(divide_remainder_round_positive, mpz_cdiv_r)
DEFINE_II_I(divide_remainder_round_negative, mpz_fdiv_r)

DEFINE_II_II(divide_quotient_remainder_round_zero, mpz_tdiv_qr)
DEFINE_II_II(divide_quotient_remainder_round_positive, mpz_cdiv_qr)
DEFINE_II_II(divide_quotient_remainder_round_negative, mpz_fdiv_qr)

DEFINE_II_I(mod, mpz_mod)
DEFINE_II_B(divisible, mpz_divisible_p)
DEFINE_III_B(congruent, mpz_congruent_p)

/* FIXME: bit shifts */
/* FIXME: power functions */

DEFINE_II_I(binary_xor,^)
DEFINE_II_I(binary_and,&)
DEFINE_II_I(binary_or,|)
DEFINE_II_I(binary_shift_left,<<)
DEFINE_II_I(binary_shift_right,>>)

DEFINE_II_B(less,<)
DEFINE_II_B(less_equal,<=)
DEFINE_II_B(greater,>)
DEFINE_II_B(greater_equal,>)
DEFINE_II_B(equal,==)
DEFINE_II_B(not_equal,!=)

KEW_METHOD(Max)
{
  check_int(args[0]);
  results[0] = KewNewInt(MAX(value(receiver), value(args[0])));
}

KEW_METHOD(Min)
{
  check_int(args[0]);
  results[0] = KewNewInt(MIN(value(receiver), value(args[0])));
}

KEW_METHOD(Abs)
{
  results[0] = KewNewInt(ABS(value(receiver)));
}

KEW_METHOD(BinaryNot)
{
    results[0] = KewNewInt(~(value(receiver)));
}

KEW_METHOD(Clamp)
{
  check_int(args[0]);
  check_int(args[1]);
  results[0] = KewNewInt(CLAMP(value(receiver), value(args[0]), value(args[1])));
}

KEW_METHOD(Hash)
{
  results[0] = (KewObject *) receiver;
}

KEW_METHOD(String)
{
  results[0] = KewWrapString(g_strdup_printf("%u", value(receiver)));
}

KEW_METHOD(To)
{
  check_int(args[0]);
  results[0] = KewNewRange(receiver, args[0]);
}

KEW_METHOD(Integer)
{
  results[0] = (KewObject *) receiver;
}

KEW_METHOD(Real)
{
  results[0] = KewNewReal((double) value(receiver));
}

KewObjectType KewIntType = {
  __FILE__, __LINE__, sizeof(KewIntegerObject), 0,
  {
    {{"hash", 0, 1},  Hash},
    {{"+", 1, 1},  Plus},
    {{"-", 1, 1},  Minus},
    {{"*", 1, 1},  Multiply},
    {{"/", 1, 1},  Divide},
    {{"%", 1, 1},  Modulo},
    {{"binaryXor", 1, 1}, BinaryXor},
    {{"binaryAnd", 1, 1}, BinaryAnd},
    {{"binaryOr", 1, 1}, BinaryOr},
    {{"binaryNot", 0, 1}, BinaryNot},
    {{"binaryShiftLeft", 1, 1}, BinaryShiftLeft},
    {{"binaryShiftRight", 1, 1}, BinaryShiftRight},
    {{"<", 1, 1},  Less},
    {{"<=", 1, 1},  LessEqual},
    {{">", 1, 1},  Greater},
    {{">=", 1, 1},  GreaterEqual},
    {{"=", 1, 1},  Equal},
    {{"!=", 1, 1},  NotEqual},
    {{"max", 1, 1},  Max},
    {{"min", 1, 1},  Min},
    {{"abs", 0, 1},  Abs},
    {{"clamp", 2, 1},  Clamp},
    {{"string", 0, 1},  String},
    {{"to", 1, 1},  To},
    {{"integer", 0, 1},  Integer},
    {{"real", 0, 1},  Real},
    {{NULL, 0, 0}, NULL}
  }
};


KewObject *
KewNewInt(int i)
{
  KewObject *result;

  result = KewNewObject(&KewIntType);
  value(result) = i;
  return result;
}

int
KewIntVal(KewObject *object)
{
  KewCheckType(object, &KewIntType, "integer");
  return value(object);
}
